﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using Microsoft.Win32;

namespace Senthil.Mobile.WhereAmI
{
    public class RouteTracker : IRouteTracker
    {   
        Route currentRoute;
        Location lastKnownLocation;
        TimeSpan lastKnownTimeSpanToDestination;

        LocationGroup nextSignificantLocationGroup;
        IEnumerable<LocationGroup> furtherLocationGroups;
        string currentLocationName = "";

        public event EventHandler<TimeChangedEventArgs> TimeChanged;
        public event EventHandler<LocationChangedEventArgs> LocationChanged;

        ILocationChangeNotifier locationChangeNotifier;

        public RouteTracker(Route route, ILocationChangeNotifier locationChangeNotifier)
        {
            this.currentRoute = route;
            this.locationChangeNotifier = locationChangeNotifier;
        }

        public void Initialize()
        {
            locationChangeNotifier.LocationChanged += new EventHandler<LocationChangeNotificationEventArgs>(locationChangeNotifier_LocationChanged);
            locationChangeNotifier.Initialize();
        }

        void locationChangeNotifier_LocationChanged(object sender, LocationChangeNotificationEventArgs args)
        {
            ProcessCellLocationChange(args.NewLocation, args.CellTowerId);
        }

        protected virtual void RaiseLocationChanged(Location location, LocationGroup nextSignificantLocationGroup,
            IEnumerable<LocationGroup> locationGroupsAfter,
            Location lastKnownLocation)
        {
            if (LocationChanged != null)
                LocationChanged(this, new LocationChangedEventArgs(location, nextSignificantLocationGroup, locationGroupsAfter, lastKnownLocation));
        }

        protected virtual void RaiseTimeChanged(TimeSpan timeToDestination, TimeSpan? timeToNextSignificantLocationGroup)
        {
            if (TimeChanged != null)
                TimeChanged(this, new TimeChangedEventArgs(timeToDestination, timeToNextSignificantLocationGroup));
        }

        private Location GetLocationForNameAndCellTowerId(string locationName, string cellTowerId)
        {
            Location location = null;
            if (lastKnownLocation == null)
            {
                currentRoute.TryGetFirstLocationWithNameAndCellTowerId(locationName, cellTowerId, out location);
            }
            else
            {
                currentRoute.TryGetClosestLocation(locationName, cellTowerId, lastKnownLocation, out location);
            }

            return location;
        }

        private void ProcessCellLocationChange(string newLocationName, string cellTowerId)
        {
            newLocationName = newLocationName.Trim();
            currentLocationName = newLocationName;

            if (currentRoute != null)
            {
                Location location = GetLocationForNameAndCellTowerId(newLocationName, cellTowerId);
                if (location != null)
                {
                    ProcessKnownLocation(location);
                }
                else
                {
                    ProcessUnknownLocation(newLocationName, cellTowerId);
                }
            }
        }

        private void ProcessUnknownLocation(string locationName, string cellTowerId)
        {
            Location tempLocation = new Location(locationName, cellTowerId, false);
            LocationGroup group = currentRoute.TryGetApproximateLocationGroupBasedOnCellTowerId(cellTowerId);

            tempLocation.LocationGroup = group;
            
            if (group != null)
            {
                nextSignificantLocationGroup = currentRoute.GetNextMajorLocationGroup(tempLocation);
                furtherLocationGroups = currentRoute.GetLocationGroupsAfter(tempLocation);
            }

            RaiseLocationChanged(tempLocation,
                       nextSignificantLocationGroup,
                       furtherLocationGroups,
                       lastKnownLocation);
        }

        private void ProcessKnownLocation(Location location)
        {
            Location previousLastKnownLocation = lastKnownLocation;

            LocationGroup group = location.LocationGroup;
            
            if (group != null)
            {
                furtherLocationGroups = currentRoute.GetLocationGroupsAfter(location).Distinct();

                nextSignificantLocationGroup = furtherLocationGroups.FirstOrDefault((locationGroup) => locationGroup.IsSpecial);
                
                RaiseLocationChanged(location,
                    nextSignificantLocationGroup,
                    furtherLocationGroups,
                    previousLastKnownLocation);

                lastKnownLocation = location;
                RecalculateTime();
            }
        }

        public void RecalculateTime()
        {
            TimeSpan timeSpanToDestination = GetTimeSpanFor(currentRoute.DestinationGroup);
            this.lastKnownTimeSpanToDestination = timeSpanToDestination;

            TimeSpan? timeSpanToNextSignificantLocationGroup = null;
            if (nextSignificantLocationGroup != null)
            {
                timeSpanToNextSignificantLocationGroup = currentRoute.GetAverageTimeTaken(lastKnownLocation, nextSignificantLocationGroup.Locations);
            }

            RaiseTimeChanged(timeSpanToDestination, timeSpanToNextSignificantLocationGroup);
        }

        public bool CanCalculateTimeSpan()
        {
            return lastKnownLocation != null;
        }

        public TimeSpan GetTimeSpanFor(LocationGroup locationGroup)
        {
            if (lastKnownLocation == null)
                throw new InvalidOperationException("There is no last known location to calculate time span.");

            return currentRoute.GetAverageTimeTaken(lastKnownLocation, locationGroup.Locations);
        }

        public LocationGroup DestinationLocationGroup
        {
            get { return currentRoute.DestinationGroup; }
        }

        public LocationGroup[] LocationGroups
        {
            get { return currentRoute.LocationGroups; }
        }

        public void GetLastKnownInfo(out Location lastKnownLocation, out TimeSpan timeSpanToDestination)
        {
            lastKnownLocation = this.lastKnownLocation;
            timeSpanToDestination = this.lastKnownTimeSpanToDestination;
        }
    }
}
